package org.aspire.io;

import java.io.File;
import java.io.InputStream;
import java.net.URL;

/******************************************************************************
 * Class to get external resources object, such as <code>java.io.File</code>,
 * <code>java.io.InputStream</code>, and <code>java.net.URL</code>.
 * <p>
 * 
 * There are four types mode of how <code>FileIO</code> getting the external
 * resources object : <br>
 * <ul>
 * <li>{@link #CLASS_URL}</li>
 * <li>{@link #WORKING_DIRECTORY}</li>
 * <li>{@link #SYSTEM_LOADER}, and</li>
 * <li>{@link #CLASS_LOADER}</li>
 * </ul>
 * <p>
 * 
 * By default <code>BaseIO</code> class is using <code>CLASS_URL</code>.
 * 
 * @author Golden T Studios (GTGE)
 *****************************************************************************/
public final class FileIO {

	// ========================================================================
	// Constants
	// ========================================================================

	/******************************************************************************
	 * File IO using class URL.
	 *****************************************************************************/
	public static final int CLASS_URL 			= 1;
	
	/******************************************************************************
	 * File IO using the current working directory.
	 *****************************************************************************/
	public static final int WORKING_DIRECTORY 	= 2;
	
	/******************************************************************************
	 * File IO using class loader.
	 *****************************************************************************/
	public static final int CLASS_LOADER		= 3;
	
	/******************************************************************************
	 * File IO using system loader.
	 *****************************************************************************/
	public static final int SYSTEM_LOADER		= 4;
	
	// ========================================================================
	// Static Methods
	// ========================================================================

	// ========================================================================
	// Fields
	// ========================================================================

	/******************************************************************************
	 * The base class which will be used if class URL is selected as File IO mode.
	 *****************************************************************************/
	private Class<?> base;
	
	/******************************************************************************
	 * The class loader derived from the base class which will be used if class
	 * loader is selected as File IO mode.
	 *****************************************************************************/
	private ClassLoader loader;

	/******************************************************************************
	 * The file IO mode to be used.
	 *****************************************************************************/
	private int mode;
	
	// ========================================================================
	// Constructors
	// ========================================================================

	/**************************************************************************
	 * Construct a new FileIO with the specified class as the base loader, and
	 * specified IO mode.
	 * 
	 * @param base the base class loader.
	 * @param mode the file IO mode.
	 *************************************************************************/
	public FileIO(Class<?> base, int mode) {
		this.base = base;
		this.loader = this.base.getClassLoader();
		this.mode = mode;
	}
	
	/**************************************************************************
	 * Construct a new FileIO with the specified class as the base loader, and
	 * using class URL as file IO mode.
	 * 
	 * @param base the base class loader.
	 *************************************************************************/
	public FileIO(Class<?> base) {
		this(base, FileIO.CLASS_URL);
	}
	
	// ========================================================================
	// Getters & Setters
	// ========================================================================
   
	/**************************************************************************
	 * Returns the base class where the resources will be taken from.
	 * @return The base {@link Class}.
	 * @see #setBase(Class)
	 *************************************************************************/
	public Class<?> getBase() {
		return this.base;
	}
	   
	/**************************************************************************
	 * Returns the class loader associated with this {@link BaseIO}.
	 * @return The {@link ClassLoader}.
	 * @see #setBase(Class)
	 *************************************************************************/
	public ClassLoader getLoader() {
		return this.loader;
	}

	/**************************************************************************
	 * Returns the default IO mode used for getting the resources.
	 * @return The default IO mode.
	 * @see #setMode(int)
	 * @see #getModeString(int)
	 *************************************************************************/
	public int getMode() {
		return this.mode;
	}
	   

	/**************************************************************************
	 * Sets the base class where the resources will be taken from.
	 * @param base The base {@link Class}.
	 * @see #getBase()
	 *************************************************************************/
	public void setBase(Class<?> base) {
		this.base = base;
		this.loader = base.getClassLoader();
	}
	
	/**************************************************************************
	 * Sets the default IO mode used for getting the resources.
	 * @param mode The new default io mode.
	 * @see #getMode()
	 * @see #CLASS_URL
	 * @see #WORKING_DIRECTORY
	 * @see #CLASS_LOADER
	 * @see #SYSTEM_LOADER
	 *************************************************************************/
	public void setMode(int mode) {
		this.mode = mode;
	}
	   	   
	// ========================================================================
	// Override Methods
	// ========================================================================

	// ========================================================================
	// Methods
	// ========================================================================

	/**************************************************************************
	 * Returns URL from specified path with specified mode.
	 * 
	 * @param path The path to get the URL from.
	 * @param mode The mode to retrieve the URL.
	 * @return the URL.
	 *************************************************************************/
	public URL getURL(String path, int mode) {
		URL url = null;
	      
		try {
			switch (mode) {
				case FileIO.CLASS_URL:
	               url = this.base.getResource(path);
	               break;
	            
	            case FileIO.WORKING_DIRECTORY:
	               File f = new File(path);
	               if (f.exists()) {
	                  url = f.toURI().toURL();
	               }
	               break;
	            
	            case FileIO.CLASS_LOADER:
	               url = this.loader.getResource(path);
	               break;
	            
	            case FileIO.SYSTEM_LOADER:
	               url = ClassLoader.getSystemResource(path);
	               break;
			}
		} catch (Exception e) {
		}
	      
		if (url == null) {
			throw new RuntimeException(this.getException(path, mode, "getURL"));
		}
	      
		return url;
    }
	
	/**************************************************************************
    * Returns URL from specified path with this {@link FileIO} default mode.
    * 
    * @param path The path to retrieve the URL from.
    * @return The {@link URL} of the given path.
    * @see #getMode()
	 *************************************************************************/
	public URL getURL(String path) {
		URL url = null;
	      
		try {
			url = this.getURL(path, this.mode);
		} catch (Exception e) {
		
		}
	      
		if (url == null) {
			// smart resource locater
			int smart = 0;
			while (url == null
					&& !this.getModeString(++smart).equals("[UNKNOWN-MODE]")) {
				try {
	               url = this.getURL(path, smart);
	            } catch (Exception e) {
	            }
			}
	         
			if (url == null) {
				throw new RuntimeException(this.getException(path, this.mode,
	                    "getURL"));
			}
	         
			this.mode = smart;
		}
	      
		return url;
	}

	/**************************************************************************
    * Returns {@link InputStream} from specified path with specified mode.
    * 
    * @param path The path to retrieve an {@link InputStream} from.
    * @param mode The mode to use for retrieving the {@link InputStream}.
    * @return The {@link InputStream}.
	 *************************************************************************/
	public InputStream getStream(String path, int mode) {
		InputStream stream = null;
	      
		try {
			switch (mode) {
				case FileIO.CLASS_URL:
					stream = this.base.getResourceAsStream(path);
					break;
	            
	            case FileIO.WORKING_DIRECTORY:
	            	stream = new File(path).toURI().toURL().openStream();
	            	break;
	            
	            case FileIO.CLASS_LOADER:
	            	stream = this.loader.getResourceAsStream(path);
	            	break;
	            
	            case FileIO.SYSTEM_LOADER:
	            	stream = ClassLoader.getSystemResourceAsStream(path);
	            	break;
			}
		} catch (Exception e) {
			
		}
	      
		if (stream == null) {
			throw new RuntimeException(this.getException(path, mode,
					"getStream"));
		}
	      
		return stream;
	}
	
	/**************************************************************************
    * Returns input stream from specified path with this <code>FileIO</code>
    * default mode.
    * 
    * @param path The path to retrieve an {@link InputStream} from.
    * @return The {@link InputStream}.
	 *************************************************************************/
	public InputStream getStream(String path) {
		InputStream stream = null;
	      
		try {
			stream = this.getStream(path, this.mode);
		} catch (Exception e) {
			
		}
	      
		if (stream == null) {
			// smart resource locater
			int smart = 0;
			while (stream == null && !this.getModeString(++smart)
					.equals("[UNKNOWN-MODE]")) {
				try {
					stream = this.getStream(path, smart);
				} catch (Exception e) {
					
	            }
			}
	         
			if (stream == null) {
				throw new RuntimeException(this.getException(path, this.mode,
	                    "getStream"));
			}
	         
			this.mode = smart;
		}
	      
		return stream;
	}
	
	/**************************************************************************
	 * Return file from specified path with specified mode.
	 * 
	 * @param path The path to retrieve an {@link File} from.
	 * @param mode The mode to use for retrieving the {@link File}.
	 * @return The {@link File}.
	 *************************************************************************/
	public File getFile(String path, int mode) {
		File file = null;
	      
		try {
			switch (mode) {
				case FileIO.CLASS_URL:
					file = new File(this.base.getResource(path).getFile()
	                       .replaceAll("%20", " "));
					break;
	            
	            case FileIO.WORKING_DIRECTORY:
	            	file = new File(path);
	            	break;
	            
	            case FileIO.CLASS_LOADER:
	            	file = new File(this.loader.getResource(path).getFile()
	                       .replaceAll("%20", " "));
	            	break;
	            
	            case FileIO.SYSTEM_LOADER:
	            	file = new File(ClassLoader.getSystemResource(path)
	                       .getFile().replaceAll("%20", " "));
	            	break;
			}
		} catch (Exception e) {
		}
	      
		if (file == null) {
			throw new RuntimeException(this.getException(path, mode, "getFile"));
		}
	      
		return file;
	}
	   
	/**************************************************************************
	 * Returns file from specified path with this <code>FileIO</code> default
	 * mode.
	 * <p>
	 * File object usually used only for writing to disk.
	 * <p>
	 * <b>Caution:</b> always try to avoid using <code>java.io.File</code>
	 * object (this method), because <code>java.io.File</code> is system
	 * dependent and not working inside jar file, use <code>java.net.URL</code>
	 * OR <code>java.io.InputStream</code> instead.
	 * 
	 * @param path The path to retrieve an {@link File} from.
	 * @return The {@link File}.
	 *************************************************************************/
	public File getFile(String path) {
		File file = null;
	      
		try {
			file = this.getFile(path, this.mode);
		} catch (Exception e) {
	    
		}
	      
		if (file == null) {
			// smart resource locater
			int smart = 0;
			while (file == null && !this.getModeString(++smart)
					.equals("[UNKNOWN-MODE]")) {
				try {
					file = this.getFile(path, smart);
	            } catch (Exception e) {
	            	
	            }
			}
	         
			if (file == null) {
				throw new RuntimeException(this.getException(path, this.mode,
	                    "getFile"));
			}
	         
			this.mode = smart;
		}
	      
		return file;
	}
	   
	/**************************************************************************
	 * Returns file on specified path with specified mode for processing.
	 * 
	 * @param path The path to retrieve a {@link File} from.
	 * @param mode The mode to use for retrieving the {@link File}.
	 * @return The {@link File}.
	 *************************************************************************/
	public File setFile(String path, int mode) {
		File file = null;
	      
		try {
			switch (mode) {
				case FileIO.CLASS_URL:
					file = new File(this.base.getResource("").getFile()
							.replaceAll("%20", " ")
							+ File.separator + path);
					break;
	            
	            case FileIO.WORKING_DIRECTORY:
	            	file = new File(path);
	            	break;
	            
	            case FileIO.CLASS_LOADER:
	            	file = new File(this.loader.getResource("").getFile()
	            			.replaceAll("%20", " ")
	            			+ File.separator + path);
	            	break;
	            
	            case FileIO.SYSTEM_LOADER:
	            	file = new File(ClassLoader.getSystemResource("").getFile()
	            			.replaceAll("%20", " ")
	            			+ File.separator + path);
	            	break;
			}
		} catch (Exception e) {
			
		}
	      
		if (file == null) {
			throw new RuntimeException(this.getException(path, mode, "setFile"));
		}
	      
		return file;
	}
	   
	/**************************************************************************
	 * Returns file on specified path with this <code>FileIO</code> default
	 * mode for processing.
	 * 
	 * @param path The path to retrieve an {@link File} from.
	 * @return The {@link File}.
	 *************************************************************************/
	public File setFile(String path) {
		File file = null;
	      
		try {
			file = this.setFile(path, this.mode);
		} catch (Exception e) {
			
		}
	      
		if (file == null) {
			// smart resource locater
			int smart = 0;
			while (file == null && !this.getModeString(++smart)
					.equals("[UNKNOWN-MODE]")) {
				try {
					file = this.setFile(path, smart);
	            } catch (Exception e) {
	            	
	            }
			}
	         
			if (file == null) {
				throw new RuntimeException(this.getException(path, this.mode,
	                    "setFile"));
			}
	         
			this.mode = smart;
		}
	      
		return file;
	}
	   
	/**************************************************************************
	 * Returns the root path of this {@link FileIO} if using specified mode. 
	 * The root path is the root where all the resources will be taken from.
	 * <p>
	 * For example : <br>
	 * The root path = "c:\games\spaceinvader" <br>
	 * The resource name = "images\background.png" <br>
	 * The resource then will be taken from = <br>
	 * "c:\games\spaceinvader\images\background.png"
	 * 
	 * @param mode The mode to retrieve root path for.
	 * @return The root path of the given mode.
	 *************************************************************************/
	public String getRootPath(int mode) {
		switch (mode) {
			case FileIO.CLASS_URL:
				return this.base.getResource("").toString();
	            
			case FileIO.WORKING_DIRECTORY:
	            return System.getProperty("user.dir") + File.separator;
	            
			case FileIO.CLASS_LOADER:
	            return this.loader.getResource("").toString();
	            
			case FileIO.SYSTEM_LOADER:
	            return ClassLoader.getSystemResource("").toString();
		}
	      
		return "[UNKNOWN-MODE]";
	}
	
    /**************************************************************************
     * Returns exception string used whenever resource can not be found.
     * 
     * @param path The path that was retrieved.
     * @param mode The IO mode used during the exception occurred.
     * @param method The method the exception occurred in.
     * @return The exception description string.
     *************************************************************************/
	protected String getException(String path, int mode, String method) {
		return "Resource not found (" + this + "): " + this.getRootPath(mode) 
				+ path;
	}

	/**************************************************************************
	 * Returns the official statement of specified IO mode, or
	 * <code>[UNKNOWN-MODE]</code> if the IO mode is undefined.
	 * 
	 * @param mode The mode to get a string representation for.
	 * @return The {@link String} representation for the given mode.
	 * @see #getMode()
	 *************************************************************************/
	public String getModeString(int mode) {
		switch (mode) {
			case FileIO.CLASS_URL:
				return "Class-URL";
			case FileIO.WORKING_DIRECTORY:
	            return "Working-Directory";
			case FileIO.CLASS_LOADER:
	            return "Class-Loader";
			case FileIO.SYSTEM_LOADER:
	            return "System-Loader";
		}
	      
		return "[UNKNOWN-MODE]";
	}
	
	// ========================================================================
	// Inner & Anonymous Classes
	// ========================================================================
}